/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2003 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: crypt7.c,v 1.4 2005/07/12 23:12:29 pullmoll Exp $
 *****************************************************************************/
#include "crypt.h"
#if	CRYPT7_TEST
#define	xmalloc(size) malloc(size)
#define	xcalloc(num,size) calloc(num,size)
#define	xfree(ptr) if (NULL != ptr) { free(ptr); ptr = NULL; }
#else
#include "memalloc.h"
#endif
#include "logger.h"
#include "twofish_aes.h"


#define	CRYPT7_MAGIC	(('2'<<0)|('F'<<8)|('S'<<16)|('H'<<24))
/*
 * The maximum key size for Twofish is 256 bits.  Other acceptable sizes are
 * 192 bits and 128 bits.  This is in bits, not bytes.
 */
#define KEYSIZE			MAX_KEY_BITS
/*
 * Number of bits needed to represent an initialisation vector (iv)
 * Do not change this!
 */
#define IVSIZE			BLOCK_SIZE


/* the configuration of this instance */
typedef struct crypt7_s {
	u_int32_t magic;
	keyInstance ki;		/* key information, including tables */
	cipherInstance ci;	/* mode and initialization vector (iv) */
}	crypt7_t;


#if	DEBUG
/* local functions */
static char *twofish_strerror(int errnum);
#endif


/***********************************************************************
 *  crypto()
 ***********************************************************************/
int crypt7_crypto(void)
{
	FUN("crypt7_crypto");

	/* no initialization required */

	return 0;
}

/***********************************************************************
 *  init() - Setup the key and initialisation vector (iv)
 ***********************************************************************/
int crypt7_init(void **pptr, int init, void *data, size_t size)
{
	crypt7_t *c;
	int rc;
	FUN("crypt7_init");

	if (NULL == pptr) {
		LOGS(L_CRYPTO,L_ERROR,("pptr is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (0 != init) {
		LOGS(L_CRYPTO,L_ERROR,("init is not 0\n"));
		errno = EINVAL;
		return -1;
	}
	c = (crypt7_t *)xcalloc(sizeof(crypt7_t), 1);
	*pptr = c;

	/* Initialise ki and ci */
	rc = makeKey(&(c->ki), DIR_ENCRYPT, KEYSIZE, NULL);
	if (TRUE != rc) {
		LOGS(L_CRYPTO,L_ERROR,("makeKey: %s\n", twofish_strerror(rc)));
		errno = EINVAL;
		return -1;
	}
	rc = cipherInit(&(c->ci), MODE_CBC, NULL);
	if (TRUE != rc) {
		LOGS(L_CRYPTO,L_ERROR,("cipherInit: %s\n", twofish_strerror(rc)));
		errno = EINVAL;
		return -1;
	}

	/* Fill up the key and iv with PRNG data */
	if (size < (KEYSIZE / 8 + IVSIZE / 8)) {
		LOGS(L_CRYPTO,L_ERROR,("not enough PRNG data\n"));
		errno = EINVAL;
		return -1;
	}
	memcpy(c->ki.key32, data, KEYSIZE / 8);
	memcpy(c->ci.iv32, data + KEYSIZE / 8, IVSIZE / 8);

	/* Initialise the Twofish key schedule from key32 */
	rc = reKey(&(c->ki));
	if (TRUE != rc) {
		LOGS(L_CRYPTO,L_ERROR,("reKey: %s\n", twofish_strerror(rc)));
		errno = EINVAL;
		return -1;
	}
	c->magic = CRYPT7_MAGIC;

	return 0;
}

/***********************************************************************
 *  exit()
 ***********************************************************************/
int crypt7_exit(void *ptr)
{
	crypt7_t *c = ptr;
	FUN("crypt7_exit");

	if (NULL == ptr) {
		LOGS(L_CRYPTO,L_ERROR,("ptr is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (CRYPT7_MAGIC != c->magic) {
		LOGS(L_CRYPTO,L_ERROR,("invalid magic %x (expected %x)\n",
			c->magic, CRYPT7_MAGIC));
		errno = EINVAL;
		return -1;
	}

	xfree(ptr);
	return 0;
}

/***********************************************************************
 *  encrypt_msg()
 *  insize - the size of plaintext
 *  *outsize - return set to to the size of ciphertext
 ***********************************************************************/
int crypt7_encrypt_msg(void *ptr, void *ciphertext, void *plaintext,
	size_t *outsize, size_t insize)
{
	crypt7_t *c = (crypt7_t *)ptr;
	u_int8_t *ct = (u_int8_t *)ciphertext;
	u_int8_t *pt = (u_int8_t *)plaintext;
	u_int32_t xinsize = (u_int32_t)insize;/* we need an exact type for insize */
	int x;
	size_t esize;  /* expanded size, for blockEncrypt */
	size_t bc;  /* # of bits ciphered */
	FUN("crypt7_encrypt_msg");

	/* check args */
	if (NULL == c || NULL == ciphertext || NULL == plaintext) {
		LOGS(L_CRYPTO,L_ERROR,("one or more arguments are NULL"));
		errno = EINVAL;
		return -1;
	}
	if (CRYPT7_MAGIC != c->magic) {
		LOGS(L_CRYPTO,L_ERROR,("invalid magic %x (expected %x)\n",
			c->magic, CRYPT7_MAGIC));
		errno = EINVAL;
		return -1;
	}

	/* blockEncrypt requires plaintext to be a multiple of 16 bytes */
	x = (xinsize + sizeof(xinsize)) % (BLOCK_SIZE / 8);
	if (0 == x) {
		esize = xinsize + sizeof(xinsize);
	} else {
		esize = xinsize + sizeof(xinsize) + ((BLOCK_SIZE / 8) - x);
	}
	if (esize > *outsize) {
		LOGS(L_CRYPTO,L_ERROR,("outsize too small (have %x, want %x)\n",
			(unsigned)*outsize, (unsigned)esize));
		errno = ENOMEM;
		return -1;
	}
	/*
	 * Put the actual size of the plaintext at the end, so we know what the size
	 * of the plaintext is after we decrypt it (we don't want the padding to be
	 * considered part of the plaintext).
	 * (This is the endian safe way to do it; we use LSB first)
	 */
	pt[esize - sizeof(xinsize) + 0] = (u_int8_t)(xinsize >>  0);
	pt[esize - sizeof(xinsize) + 1] = (u_int8_t)(xinsize >>  8);
	pt[esize - sizeof(xinsize) + 2] = (u_int8_t)(xinsize >> 16);
	pt[esize - sizeof(xinsize) + 3] = (u_int8_t)(xinsize >> 24);

	/* perform the encryption */
	LOGS(L_CRYPTO,L_DEBUGX,("calling blockEncrypt with %d bytes\n",
		(int)esize));
	bc = blockEncrypt(&(c->ci), &(c->ki), pt, esize * 8, ct);
	if (esize * 8 != bc) {
		LOGS(L_CRYPTO,L_ERROR,("blockEncrypt (%d bytes): %s\n",
			(int)esize, twofish_strerror(bc)));
		errno = EINVAL;
		return -1;
	}

	/* return ciphertext size */
	*outsize = esize;
	return 0;
}

/***********************************************************************
 *  decrypt_msg()
 *  insize - the size of ciphertext
 *  *outsize - return set to to the size of plaintext
 ***********************************************************************/
int crypt7_decrypt_msg(void *ptr, void *plaintext, void *ciphertext,
	size_t *outsize, size_t insize)
{
	crypt7_t *c = (crypt7_t *)ptr;
	u_int8_t *pt = (u_int8_t *)plaintext;
	u_int8_t *ct = (u_int8_t *)ciphertext;
	u_int32_t rsize;  /* real size of the plaintext (minus padding) */ 
	size_t bc;  /* # of bits deciphered */
	FUN("crypt7_decrypt_msg");

	/* check args */
	if (NULL == ptr) {
		LOGS(L_CRYPTO,L_ERROR,("ptr is NULL"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == plaintext) {
		LOGS(L_CRYPTO,L_ERROR,("plaintext is NULL"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == ciphertext) {
		LOGS(L_CRYPTO,L_ERROR,("ciphertext is NULL"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == outsize) {
		LOGS(L_CRYPTO,L_ERROR,("outsize is NULL"));
		errno = EINVAL;
		return -1;
	}
	if (CRYPT7_MAGIC != c->magic) {
		LOGS(L_CRYPTO,L_ERROR,("invalid magic %x (expected %x)\n",
			c->magic, CRYPT7_MAGIC));
		errno = EINVAL;
		return -1;
	}

	/* blockDecrypt requires ciphertext to be a multiple of 16 bytes */
	if (0 != (insize % (BLOCK_SIZE / 8))) {
		/*
		 * This should not happen unless there was an error in transmission.
		 * If there are any errors in transmission then all future decryptions
		 * would fail as well because iv will be wrong.
		 */
		LOGS(L_CRYPTO,L_ERROR,("ciphertext is not a multiple of 16 bytes\n"));
		errno = EINVAL;
		return -1;
	}

	/* perform the decryption */
	LOGS(L_CRYPTO,L_DEBUGX,("calling blockDecrypt with %d bytes\n",
		(int)insize));
	bc = blockDecrypt(&(c->ci), &(c->ki), ct, insize * 8, pt);
	if (insize * 8 != bc) {
		LOGS(L_CRYPTO,L_ERROR,("blockDecrypt (%d bytes): %s\n",
			(int)insize, twofish_strerror(bc)));
		errno = EINVAL;
		return -1;
	}

	/*
	 * get the real size of the plaintext
	 * it is stored in LSB first byte order
	 */
	rsize =
		((u_int32_t)pt[insize - sizeof(rsize) + 0] <<  0) |
		((u_int32_t)pt[insize - sizeof(rsize) + 1] <<  8) |
		((u_int32_t)pt[insize - sizeof(rsize) + 2] << 16) |
		((u_int32_t)pt[insize - sizeof(rsize) + 3] << 24);
	LOGS(L_CRYPTO,L_DEBUGX,("real plaintext size is %d bytes\n",
		(int)rsize));
	/* return plaintext size -- this should always be <= insize */
	*outsize = rsize;
	return 0;
}

#if	DEBUG
/*
 * Return an error string from a twofish2.c error number
 */
static char *twofish_strerror(int errnum)
{
	switch (errnum) {
		case BAD_KEY_DIR:
			return "Key direction is invalid (unknown value)";
		case BAD_KEY_MAT:
			return "Key material not of correct length";
		case BAD_KEY_INSTANCE:
			return "Key passed is not valid";
		case BAD_CIPHER_MODE:
			return "Params struct passed to cipherInit invalid";
		case BAD_CIPHER_STATE:
			return "Cipher in wrong state (e.g., not initialised)";
		case BAD_INPUT_LEN:
			return "inputLen not a multiple of block size";
		case BAD_PARAMS:
			return "Invalid parameters";
		case BAD_IV_MAT:
			return "Invalid IV text";
		case BAD_ENDIAN:
			return "Incorrect endianness define";
		case BAD_ALIGN32:
			return "Incorrect 32-bit alignment";
		default:
			return "Unknown Twofish error";
	}
}
#endif

#if	CRYPT7_TEST
configuration_t *g_conf = NULL;
#define	SIZE	1024

int pm_vsnprintf(char *dst, size_t size, const char *fmt, va_list ap)
{
	return vsnprintf(dst, size, fmt, ap);
}

int pm_snprintf(char *dst, size_t size, const char *fmt, ...)
{
	va_list ap;
	int len;

	va_start(ap, fmt);
	len = vsnprintf(dst, size, fmt, ap);
	va_end(ap);
	return len;
}

#define	PLAINTEXT	"The quick brown fox jumps over the lazy dog. Kaufen Sie jede Woche vier gute bequeme Pelze xy. 0 1 2 3 4 5 6 7 8 9"

int main(int argc, char **argv)
{
	char rndname[MAXPATHLEN] = "/dev/urandom";
	char outname[MAXPATHLEN] = "crypt7.bin";
	FILE *rnd, *out;
	void *ctx;
	int mode = 0;
	size_t outsize = SIZE;
	size_t insize = SIZE;
	size_t size;
	size_t count = 1;
	size_t n;
	size_t initial_size;
	unsigned char *initial;
	unsigned char *plaintext;
	unsigned char *ciphertext;
	int i;
	int rc;

	for (i = 1; i < argc; i++) {
		if (0 == strcmp(argv[i], "--diehard")) {
			insize = 0x1000;
			outsize = 2 * insize;
			count = 0x1000;
		} else if (0 == strncmp(argv[i], "-r", 2) && strlen(argv[i]) > 2) {
			strcpy(rndname, &argv[i][2]);
		} else if (0 == strcmp(argv[i], "-r")) {
			strcpy(rndname, argv[++i]);
		} else if (0 == strncmp(argv[i], "-o", 2) && strlen(argv[i]) > 2) {
			strcpy(rndname, &argv[i][2]);
		} else if (0 == strcmp(argv[i], "-o")) {
			strcpy(rndname, argv[++i]);
		} else if (0 == strncmp(argv[i], "-m", 2) && strlen(argv[i]) > 2) {
			mode = strtol(&argv[i][2], NULL, 0);
		} else if (0 == strcmp(argv[i], "-m")) {
			mode = strtoul(argv[++i], NULL, 0);
		} else if (0 == strncmp(argv[i], "-s", 2) && strlen(argv[i]) > 2) {
			insize = strtoul(&argv[i][2], NULL, 0);
			outsize = 2 * insize;
		} else if (0 == strcmp(argv[i], "-s")) {
			insize = strtoul(argv[++i], NULL, 0);
			outsize = 2 * insize;
		} else if (0 == strncmp(argv[i], "-c", 2) && strlen(argv[i]) > 2) {
			count = strtoul(&argv[i][2], NULL, 0);
		} else if (0 == strcmp(argv[i], "-c")) {
			count = strtoul(argv[++i], NULL, 0);
		}
	}

	initial_size = 256;

	printf("open random source: %s\n", rndname);
	rnd = fopen(rndname, "rb");
	if (NULL == rnd) {
		perror(rndname);
		exit(1);
	}

	initial = xcalloc(initial_size, sizeof(u_int8_t));
	if (initial_size != fread(initial, 1, initial_size, rnd)) {
		perror("PRNG data");
		exit(2);
	}
	fclose(rnd);

	printf("create output file: %s\n", outname);
	out = fopen(outname, "wb");
	if (NULL == out) {
		perror(outname);
		exit(3);
	}

	plaintext = xcalloc(insize, sizeof(u_int8_t));
	ciphertext = xcalloc(outsize, sizeof(u_int8_t));

	if (0 != (rc = crypt7_init(&ctx, mode, initial, initial_size))) {
		fprintf(stderr, "crypt7_init failed (%d)\n", rc);
		exit(4);
	}

	size = sprintf(plaintext, "%s", PLAINTEXT);
	printf("%d bytes plaintext\n", size);
	outsize = size * 2;
	if (0 != (rc = crypt7_encrypt_msg(ctx, ciphertext, plaintext,
		&outsize, size))) {
		fprintf(stderr, "crypt7_encrypt_msg failed (%d)\n", rc);
		exit(5);
	}
	printf("encrypted %d bytes ciphertext\n", outsize);
	if (0 != (rc = crypt7_exit(ctx))) {
		fprintf(stderr, "crypt7_exit failed (%d)\n", rc);
		exit(6);
	}
	memset(plaintext, 0, insize);
	if (0 != (rc = crypt7_init(&ctx, mode, initial, initial_size))) {
		fprintf(stderr, "crypt7_init failed (%d)\n", rc);
		exit(7);
	}
	size = outsize;
	outsize = size * 2;
	if (0 != (rc = crypt7_decrypt_msg(ctx, plaintext, ciphertext,
		&outsize, size))) {
		fprintf(stderr, "crypt7_decrypt_msg failed (%d)\n", rc);
		exit(8);
	}
	printf("decrypted %d bytes plaintext\n", outsize);
	if (0 != memcmp(plaintext, PLAINTEXT, outsize)) {
		fprintf(stderr, "plaintext mismatch\n");
		exit(9);
	}
	printf("plaintext match\n");

	for (n = 0; n < count; n++) {
		outsize = 2 * insize;
		if (0 != crypt7_encrypt_msg(ctx, ciphertext, plaintext,
			&outsize, insize)) {
			fprintf(stderr, "crypt7_encrypt_msg failed (%d)\n", rc);
			exit(10);
		}
		if (outsize != fwrite(ciphertext, 1, outsize, out)) {
			perror("fwrite");
			exit(11);
		}
	}

	fclose(out);

	return 0;
}

#endif
